
#include "errlog.h"
#include <assert.h>


#include "Mesh_grid3d.h"

#include <ShapeImprovementWrapper.hpp>
#include <QualityImprover.hpp>
#include <ObjectiveFunction.hpp>
#include <ConditionNumberQualityMetric.hpp>
#include <LPtoPTemplate.hpp>
#include <ConjugateGradient.hpp>
#include <PatchData.hpp>
#include <IdealWeightInverseMeanRatio.hpp>
#include <MsqError.hpp>
#include <TerminationCriterion.hpp>
#include <FeasibleNewton.hpp>
#include "g3d_mesq.h"

using namespace Mesquite;

typedef struct
{
    unsigned id;
    Mesh_grid3d *mesh;
    InstructionQueue *queue;

    ConditionNumberQualityMetric *untangle_metric;
    LPtoPTemplate *untangle_objective;
    TerminationCriterion *terminate_untangle;
    ConjugateGradient *untangler;
    IdealWeightInverseMeanRatio *metric;
    LPtoPTemplate *objective;
    FeasibleNewton *smoother;
} g3d_mesh_t;

#define G3D_MESH_ID     0x6FBA1126
#define CHECK_ERR(err)  if(err) {error_msg("%s: %s",__func__, err.error_message());}

mesq_handle mesq_init(grid3d_t *grid)
{
    g3d_mesh_t *foo = new g3d_mesh_t; // (g3d_mesh_t*)calloc(1, sizeof(g3d_mesh_t));
    if (foo == NULL)
        return NULL;
    MsqError err;
    foo->mesh = new Mesh_grid3d(*grid);
    foo->id = G3D_MESH_ID;
    foo->untangle_metric = new ConditionNumberQualityMetric;
    foo->untangle_objective = new LPtoPTemplate(foo->untangle_metric, 2, err);
    CHECK_ERR(err);
    foo->untangle_objective->set_gradient_type(ObjectiveFunction::ANALYTICAL_GRADIENT);
    foo->untangler = new ConjugateGradient(foo->untangle_objective, err);
    CHECK_ERR(err);
    foo->untangler->set_patch_type(PatchData::ELEMENTS_ON_VERTEX_PATCH, err, 1, 1);
    CHECK_ERR(err);
    foo->untangler->set_patch_type(PatchData::GLOBAL_PATCH, err);
    CHECK_ERR(err);
    foo->terminate_untangle = new TerminationCriterion;
    foo->terminate_untangle->add_criterion_type_with_int(TerminationCriterion::NUMBER_OF_ITERATES, 10, err);
    CHECK_ERR(err);
    foo->untangler->set_inner_termination_criterion(foo->terminate_untangle);

    foo->metric = new IdealWeightInverseMeanRatio(err);
    CHECK_ERR(err);
    foo->objective = new LPtoPTemplate(foo->metric, 2, err);
    CHECK_ERR(err);
    foo->smoother = new FeasibleNewton(foo->objective);

    foo->queue = new InstructionQueue;
    foo->queue->add_preconditioner(foo->untangler, err);
    CHECK_ERR(err);
    foo->queue->add_preconditioner(foo->smoother, err);
    // my_queue.set_master_quality_improver(smoother, err);
    CHECK_ERR(err);
    foo->queue->set_master_quality_improver(foo->untangler, err);
    CHECK_ERR(err);

    return (mesq_handle) foo;
}

static g3d_mesh_t *get_g3d_mesh_from_handle(mesq_handle handle)
{
    g3d_mesh_t *foo = reinterpret_cast<g3d_mesh_t*> (handle);
    if ((foo == NULL) || (foo->id != G3D_MESH_ID))
        error_msg("%s: invalid mesq_handle passed.\n", __FILE__);
    return foo;
}

void mesq_done(mesq_handle handle)
{
    if (handle)
    {
        g3d_mesh_t *foo = get_g3d_mesh_from_handle(handle);
        delete foo->mesh;
        delete foo->untangle_metric;
        delete foo->untangle_objective;
        delete foo->terminate_untangle;
        delete foo->untangler;
        delete foo->metric;
        delete foo->objective;
        delete foo->smoother;
        delete foo->queue;
        delete foo;
    }
}

void mesq_run(mesq_handle handle)
{
    g3d_mesh_t *foo = get_g3d_mesh_from_handle(handle);
    Mesh_grid3d *mesh = foo->mesh;
    Mesquite::MsqError err;
    Mesquite::ShapeImprovementWrapper mesh_quality_algorithm(err);
    if (err)
    {
        error_msg("%s: error creating ShapeImprovementWrapper: %s\n",
                  __func__, err.error_message());
    }
    mesh_quality_algorithm.run_instructions(mesh, err);
    if (err)
    {
        error_msg("%s: error in run_instructions() : %s\n",
                  __func__, err.error_message());
    }
}

void mesq_run_no_wrapper(mesq_handle handle)
{
    g3d_mesh_t *foo = get_g3d_mesh_from_handle(handle);
    MsqError err;
    foo->queue->run_instructions(foo->mesh, err);
    CHECK_ERR(err);
}

void mesq_run_no_wrapper(mesq_handle handle,
                         Mesquite::MeshDomain *domain)
{
    g3d_mesh_t *foo = get_g3d_mesh_from_handle(handle);
    MsqError err;
    foo->queue->run_instructions(foo->mesh, domain, err);
    CHECK_ERR(err);
}

void mesq_run_no_wrapper(mesq_handle handle,
                         Mesquite::Mesh *mesh,
                         Mesquite::MeshDomain *domain)
{
    g3d_mesh_t *foo = get_g3d_mesh_from_handle(handle);
    MsqError err;
    foo->queue->run_instructions(mesh, domain, err);
    CHECK_ERR(err);
}




